<?php

// Namespace

namespace Loop;


/**
 * CExceptionManager is a class that handles any error and exception thrown by the application
 */

class CExceptionManager
{
	/**
	 * Initializes the exception manager
	 */
	
	public function initialize()
	{
		// Set error reporting
		
		if (pref('[Loop]/features/debug') === true)
		{
			error_reporting(pref('[Loop]/exceptions/level'));
			ini_set('error_reporting', pref('[Loop]/exceptions/level'));
			ini_set('display_errors', 'on');
		}
		else
		{
			error_reporting(null);
			ini_set('error_reporting', null);
			ini_set('display_errors', '');
		}
	}
	
	
	/**
	 * Handles PHP errors triggered by the application and renders a HTML page
	 *
	 * @param 	string 	$errorNumber 	The code of the error
	 * @param 	string 	$errorString 	The description of the error
	 * @param 	string 	$errorFile 		The path of the file in which the error occurred
	 * @param 	int 	$errorLine 		The line number where the error was detected
	 * @param 	array 	$errorContext 	An array which contains every variable that existed in the scope when the error occured
	 */
	
	public function onError($errorNumber, $errorString, $errorFile = null, $errorLine = null, $errorContext = null)
	{
		// Clear the output buffer
		
		app()->getViewManager()->clearBuffer();


		// Ignore warnings

		if (pref('[Loop]/exceptions/showWarnings') == false)
		{
			switch ($errorNumber)
			{
				case E_COMPILE_WARNING:
				case E_CORE_WARNING:
				case E_DEPRECATED:
				case E_NOTICE:
				case E_STRICT:
				case E_USER_DEPRECATED:
				case E_USER_NOTICE:
				case E_USER_WARNING:
				case E_WARNING:
				{
					return;
				}
			}
		}


		// If not in debug mode, throw an internal exception

		if (pref('[Loop]/features/debug') == false)
		{
			e(EXCEPTION_APPLICATION_ERROR_INTERNAL);
		}


		// Render the error

		$this->renderError
		(
			$errorNumber,
			'ERROR #' . $errorNumber,
			$errorString,
			$errorFile,
			$errorLine,
			$errorContext
		);
	}
	
	
	/**
	 * Handles exceptions triggered by the application and renders a HTML page
	 * Exceptions can never come from APIs since they are caught before
	 *
	 * @param 	Exception 	$exception 	The handled exception
	 */
	
	public function onException($exception)
	{
		// Clear the output buffer
		
		app()->getViewManager()->clearBuffer();


		// Save the exception

		$pathToException = path
		(
			array
			(
				PATH_TEMPORARY,
				'Exception'
			),
			true
		);

		$pathToException .= 'Exception.txt';

		file_put_contents($pathToException, json_encode($exception));


		// Have we actually received a view?

		$view = view();

		if (is_null($view) == false)
		{
			// Does the user need special rights to access this view?
			
			$exceptions = array
			(
				EXCEPTION_PERMISSION_ON_CLASS_NOT_GRANTED,
				EXCEPTION_PERMISSION_ON_OBJECT_NOT_GRANTED,
				EXCEPTION_PERMISSION_ON_VIEW_NOT_GRANTED,
				EXCEPTION_USER_ALREADY_EXISTS,
				EXCEPTION_USER_AUTHENTICATION_NEEDED,
				EXCEPTION_USER_NOT_FOUND,
				EXCEPTION_USER_PASSWORD_NOT_VALID,
				EXCEPTION_USER_TOO_MANY_FOUND
			);
			
			if
			(
				(in_array($exception->getCode(), $exceptions) === true) &&
				($view->type === VIEW_TYPE_HTML) && 
				($view->class === VIEW_CLASS_PAGE)
			)
			{
				
				// Store the current view path in session 

				if ($view->getPath() != pref('[Loop]/views/pathToPageSignIn.html'))
				{
					v('SESSION::urlOfRedirection', $view->getPath());
				}


				// Define special views that can be granted access
				
				$pathToPageSignInHtml = pref('[Loop]/views/pathToPageSignIn.html');
				$pathToPageSignInHtml = substr($pathToPageSignInHtml, strrpos($pathToPageSignInHtml, '/') + 1);
				$pathToPageSignInHtml =  substr($pathToPageSignInHtml, 0, strpos($pathToPageSignInHtml, '.html'));
				
				$pathToView = $view->getPath();
				
				
				// Are we trying to reach the sign-in page?
				
				if (strpos($pathToView, $pathToPageSignInHtml) !== false)
				{
					// Allow rendering of this view
					
					pref('[Loop]/features/authentication', false);
					pref('[Loop]/features/permissions', false);
					
					app()->getViewManager()->renderRootView();
					
					pref('[Loop]/features/authentication', true);
					pref('[Loop]/features/permissions', true);
					
					return;
				}
				else
				{
					$url = app()->getPathHelper()->convertPathToUrl(pref('[Loop]/views/pathToPageSignIn.html'));
					redirect($url);
				}
			}
		}
					
		
		// Get the context if possible
		
		$exceptionContext = null;
		
		if (get_class($exception) == 'Loop\CException')
		{
			$exceptionContext = $exception->getContext();
		}
		
		
		// Render the exception

		$this->renderError
		(
			$exception->getCode(),
			str($exception->getCode()),
			str($exception->getCode() . '?description'),
			$exception->getFile(),
			$exception->getLine(),
			$exceptionContext,
			$exception->getTrace()
		);
	}
	
	
	/**
	 * Renders an error or an exception
	 *
	 * @param 	int 	$errorCode 	 		The code of the error
	 * @param 	string 	$errorTitle 		The title of the error
	 * @param 	string 	$errorDescription 	The description of the error
	 * @param 	string 	$errorFile 			The path of the file in which the error occurred
	 * @param 	int 	$errorLine 			The line number where the error was detected
	 * @param 	array 	$errorContext 		An array which contains every variable that existed in the scope when the error occured
	 * @param 	array 	$errorTraces 		An array of associative arrays which contains the error details (concerned files, objects, classes, ...)
	 */
	
	private function renderError($errorCode, $errorTitle, $errorDescription = null, $errorFile = null, $errorLine = null, $errorContext = null, $errorTraces = null)
	{
		// Do we actually want to render the error?

		if (pref('[Loop]/exceptions/render') == false)
		{
			// No, throw a simple exception instead

			e($errorCode, $errorContext);
		}


		// Make sure there is an error trace

		if (empty($errorTraces) === true)
		{
			$errorTraces = debug_backtrace();
		}


		// Rendering is different for each mode (CGI / CLI)
		
		if (app()->isRunningCli() === true)
		{
			// Display an error

			if (fv('mute') == false)
			{
				// Title, description, code and location

				p('*** ' . $errorTitle . "\n");
			
				if (empty($errorDescription) == false)
				{
					p($errorDescription . "\n");
				}
				
				p('Code: ' . $errorCode . "\n");
				p('In: ' . $errorFile . ' (' . $errorLine . ')' . "\n");
				
				
				// Context

				if (is_array($errorContext) === true)
				{
					p('Context:' . "\n");
					
					foreach ($errorContext as $contextKey => $contextValue)
					{
						p('- ' . $contextKey . ' = ' . print_r($contextValue, true) . "\n");
					}
				}
				
				
				// Debug traces

				if (is_array($errorTraces) === true)
				{
					p('Trace:' . "\n");
					
					foreach ($errorTraces as $errorTrace)
					{
						if
						(
							(isset($errorTrace['file']) === true) &&
							(isset($errorTrace['line']) === true)
						)
						{
							p('- ' . basename($errorTrace['file']) . ' (' . $errorTrace['line'] . ')' . "\n");
						}
					}
				}
			}
			

			// Always exit with an error code

			if (is_int($errorCode) === true)
			{
				exit($errorCode);
			}
			else
			{
				exit(1);	
			}
		}
		else
		{
			try
			{
				// Clear the output buffer
				
				app()->getViewManager()->clearBuffer();
				
				
				// Ok, we don't need to check permissions anymore
				
				pref('[Loop]/features/permissions', false);
				
				
				// Prepare variables for the view
				
				if (pref('[Loop]/features/debug') === true)
				{
					$viewVariables = array
					(
						'errorCode' => $errorCode,
						'errorFile' => $errorFile,
						'errorLine' => $errorLine,
						'errorDescription' => $errorDescription,
						'errorContext' => $errorContext,
						'errorTitle' => $errorTitle,
						'errorTraces' => $errorTraces
					);
				}
				else
				{
					$viewVariables = array
					(
						'errorCode' => $errorCode,
						'errorDescription' => $errorDescription,
						'errorTitle' => $errorTitle,
					);
				}
				
				
				// Create the view (either a page or an API result)
				
				$rootView = app()->getViewManager()->getRootView();

				if
				(
					(is_object($rootView) === true) &&
					(
						($rootView->type === VIEW_TYPE_API) ||
						(
							($rootView->class != 'Page') &&
							($rootView->type != VIEW_TYPE_ACTION)
						)
					 )
				)
				{
					$view = app()->getViewManager()->createViewFromProperties('Loop', 'Json', 'Snippet', 'Api.Result', 'json');

					$viewVariables = array('apiResult' => $viewVariables);
					$view->setVariables($viewVariables);
				}
				else
				{
					$view = app()->getViewManager()->createViewFromPath(pref('[Loop]/views/pathToPageError.html'));

					$viewVariables = array('error' => $viewVariables);
					$view->setVariables($viewVariables);
				}
				
				
				// Render the error view
				
				app()->getViewManager()->setRootView($view);
				app()->getViewManager()->renderRootView();
			}
			catch (\Exception $e)
			{
				// Clear the output buffer
				
				app()->getViewManager()->clearBuffer();
				
				
				// Display the error
				
				print('<h1>' . $errorTitle . '</h1>');
				
				if (empty($errorDescription) == false)
				{
					print('<p>' . $errorDescription . '</p>');
				}
				
				if (pref('[Loop]/features/debug') === true)
				{
					print('<h2>Code</h2>');
					print('<p>' . $errorCode . '</p>');
					
					print('<h2>Location</h2>');
					print('<p>In <strong>' . $errorFile . '</strong> (' . $errorLine . ')</p>');
					
					if (is_array($errorContext) === true)
					{
						print('<h2>Context</h2>');
						print('<ul>');
						
						foreach ($errorContext as $contextKey => $contextValue)
						{
							print('<li>');
							print($contextKey . ' = ' . print_r($contextValue, true));
							print('</li>');
						}
						
						print('</ul>');
					}

					if (is_array($errorTraces) === true)
					{
						print('<h2>Trace</h2>');
						print('<ul>');
						
						foreach ($errorTraces as $errorTrace)
						{
							if
							(
								(isset($errorTraces['file']) === true) &&
								(isset($errorTraces['line']) === true)
							)
							{
								print('<li>');
								print(basename($errorTrace['file']) . ' (' . $errorTrace['line'] . ')');
								print('</li>');
							}
						}
						
						print('</ul>');
					}
				}
			}
		}
		
		
		// Make sure we quit the application

		if (pref('[Loop]/exceptions/die') === true)
		{
			die();
		}
	}
}

?>
